#ifndef _MATRIX_H_
#define _MATRIX_H_
#include <cmath>
// Column-major Matrix
// Properties :
//		element[16] : matrix data
// Description :
//		Matrix4x4 class with data map as below :
//				element[0]		element[4]		element[8]		element[12]
//				element[1]		element[5]		element[9]		element[13]
//				element[2]		element[6]		element[10]		element[14]
//				element[3]		element[7]		element[11]		element[15]

template <class Item>
class Matrix
{
private:
    Item element[16];
public:
    // Default Constructor
    // Description :
    //		Default matrix :
    //			1 0 0 0
    //			0 1 0 0
    //			0 0 1 0
    //			0 0 0 1
    Matrix();

    //
    // Copy Constructor
    // Parameter :
    //          m_Matrix : an Matrix of Item
    // Description :
    //          Create a matrix with value from m_Matrix
    Matrix(const Matrix&);

    //
    // Constructor
    // Parameter :
    //		_array : an Array of Item
    // Description :
    //		Create a matrix with value from _array
    //			_array[0]		_array[4]		_array[8]		_array[12]
    //			_array[1]		_array[5]		_array[9]		_array[13]
    //			_array[2]		_array[6]		_array[10]	_array[14]
    //			_array[3]		_array[7]		_array[11]	_array[15]
    Matrix(Item* _array);

    // Operator []
    // Parameter :
    //		offset : the offset
    // Description :
    //		Return reference to element[offset], you can change the matrix data by use this function!
    //		Exam: matrix[offset] = 10;

    inline Item & operator [] (const int offset)
    {
        return element[offset];
    }

    // Operator =
    // Parameter :
    //		rhs : The right hand side Matrix
    // Description :
    //		This Matrix = rhs

    Matrix & operator =(const Matrix& rhs);

    // Operator +
    // Parameter :
    //		rhs : The right hand side Matrix
    // Description :
    //		Return Matrix M = this Matrix + rhs
    // Note :
    //		This Matrix is not change after call this function!
    Matrix operator +(Matrix&);

    // Operator *
    // Parameter :
    //		rhs : The right hand side Matrix
    // Description :
    //		Return Matrix M = this Matrix * rhs
    // Note :
    //		This Matrix is not change after call this function!

    Matrix operator *(Matrix&);

    // Operator -
    // Parameter :
    //		rhs : The right hand side Matrix
    // Description :
    //		Return Matrix M = this Matrix - rhs
    // Note :
    //		This Matrix is not change after call this function!
    Matrix operator -(Matrix&);

    inline Item* getMatrix()
    {
        return reinterpret_cast<Item*> (element);
    }

    // transformVector(..)
    // Parameter :
    //		rhs : right hand side Vector
    // Description :
    //		Transform rhs Vector : Vector rhs = this Matrix * Vector rhs
    //void transformVector(Vector<Item>& rhs);
	//	void transformVector(Vector4<Item>& rhs);
		
    // getRowMajorMatrix()
    // Description
    //		Return A Row-major matrix 's equivalent with this Matrix
    Matrix getRowMajorMatrix();

    // this matrix become a rotateMatrix after this function
    // Pre-condition: this matrix must be Matrix<float_t>
    const Matrix& loadRotateMatrix(const Item angle,
                                   const Item ax,
                                   const Item ay,
                                   const Item az);
    const Matrix& loadTranslateMatrix(const Item dx,
                                      const Item dy,
                                      const Item dz);
    const Matrix& loadScaleMatrix(const Item x,
                                  const Item y,
                                  const Item z);
};

template <class Item>
Matrix<Item>::Matrix()
{
    element[0] = 0;
    element[4] = 0;
    element[8] = 0;
    element[12] = 0;
    element[1] = 0;
    element[5] = 0;
    element[9] = 0;
    element[13] = 0;
    element[2] = 0;
    element[6] = 0;
    element[10] = 0;
    element[14] = 0;
    element[3] = 0;
    element[7] = 0;
    element[11] = 0;
    element[15] = 0;
}

template <class Item>
Matrix<Item>::Matrix(const Matrix<Item>& m_Matrix)
{
    element[0] = m_Matrix.element[0];
    element[1] = m_Matrix.element[1];
    element[2] = m_Matrix.element[2];
    element[3] = m_Matrix.element[3];
    element[4] = m_Matrix.element[4];
    element[5] = m_Matrix.element[5];
    element[6] = m_Matrix.element[6];
    element[7] = m_Matrix.element[7];
    element[8] = m_Matrix.element[8];
    element[9] = m_Matrix.element[9];
    element[10] = m_Matrix.element[10];
    element[11] = m_Matrix.element[11];
    element[12] = m_Matrix.element[12];
    element[13] = m_Matrix.element[13];
    element[14] = m_Matrix.element[14];
    element[15] = m_Matrix.element[15];
}

template <class Item>
Matrix<Item>::Matrix(Item* _array)
{
    element[0] = _array[0];
    element[1] = _array[1];
    element[2] = _array[2];
    element[3] = _array[3];
    element[4] = _array[4];
    element[5] = _array[5];
    element[6] = _array[6];
    element[7] = _array[7];
    element[8] = _array[8];
    element[9] = _array[9];
    element[10] = _array[10];
    element[11] = _array[11];
    element[12] = _array[12];
    element[13] = _array[13];
    element[14] = _array[14];
    element[15] = _array[15];
}

template <class Item>
Matrix<Item>& Matrix<Item>::operator =(const Matrix<Item>& m_Matrix)
{
    element[0] = m_Matrix.element[0];
    element[1] = m_Matrix.element[1];
    element[2] = m_Matrix.element[2];
    element[3] = m_Matrix.element[3];
    element[4] = m_Matrix.element[4];
    element[5] = m_Matrix.element[5];
    element[6] = m_Matrix.element[6];
    element[7] = m_Matrix.element[7];
    element[8] = m_Matrix.element[8];
    element[9] = m_Matrix.element[9];
    element[10] = m_Matrix.element[10];
    element[11] = m_Matrix.element[11];
    element[12] = m_Matrix.element[12];
    element[13] = m_Matrix.element[13];
    element[14] = m_Matrix.element[14];
    element[15] = m_Matrix.element[15];
    return *this;
}

template <class Item>
Matrix<Item> Matrix<Item>::operator +(Matrix<Item>& rhs)
{
    Matrix<Item> temp;
    temp[0] = element[0] + rhs[0];
    temp[1] = element[1] + rhs[1];
    temp[2] = element[2] + rhs[2];
    temp[3] = element[3] + rhs[3];
    temp[4] = element[4] + rhs[4];
    temp[5] = element[5] + rhs[5];
    temp[6] = element[6] + rhs[6];
    temp[7] = element[7] + rhs[7];
    temp[8] = element[8] + rhs[8];
    temp[9] = element[9] + rhs[9];
    temp[10] = element[10] + rhs[10];
    temp[11] = element[11] + rhs[11];
    temp[12] = element[12] + rhs[12];
    temp[13] = element[13] + rhs[13];
    temp[14] = element[14] + rhs[14];
    temp[15] = element[15] + rhs[15];
    return temp;
}

template <class Item>
Matrix<Item> Matrix<Item>::operator -(Matrix<Item>& rhs)
{
    Matrix<Item> temp;
    temp[0] = element[0] - rhs[0];
    temp[1] = element[1] - rhs[1];
    temp[2] = element[2] - rhs[2];
    temp[3] = element[3] - rhs[3];
    temp[4] = element[4] - rhs[4];
    temp[5] = element[5] - rhs[5];
    temp[6] = element[6] - rhs[6];
    temp[7] = element[7] - rhs[7];
    temp[8] = element[8] - rhs[8];
    temp[9] = element[9] - rhs[9];
    temp[10] = element[10] - rhs[10];
    temp[11] = element[11] - rhs[11];
    temp[12] = element[12] - rhs[12];
    temp[13] = element[13] - rhs[13];
    temp[14] = element[14] - rhs[14];
    temp[15] = element[15] - rhs[15];
    return temp;
}

template <class Item>
Matrix<Item> Matrix<Item>::operator *(Matrix<Item>& rhs)
{
    Matrix<Item> temp;
    // colum 0 row 0 = M1_row0 * M2_column0
    temp[0] = element[0] * rhs[0] + element[4] * rhs[1] + element[8] * rhs[2] + element[12] * rhs[3];
    // colum 0 row 1
    temp[1] = element[1] * rhs[0] + element[5] * rhs[1] + element[9] * rhs[2] + element[13] * rhs[3];
    // colum 0 row 2
    temp[2] = element[2] * rhs[0] + element[6] * rhs[1] + element[10] * rhs[2] + element[14] * rhs[3];
    // colum 0 row 3
    temp[3] = element[3] * rhs[0] + element[7] * rhs[1] + element[11] * rhs[2] + element[15] * rhs[3];

    // colum 1 row 0
    temp[4] = element[0] * rhs[4] + element[4] * rhs[5] + element[8] * rhs[6] + element[12] * rhs[7];
    // colum 1 row 1
    temp[5] = element[1] * rhs[4] + element[5] * rhs[5] + element[9] * rhs[6] + element[13] * rhs[7];
    // colum 1 row 2
    temp[6] = element[2] * rhs[4] + element[6] * rhs[5] + element[10] * rhs[6] + element[14] * rhs[7];
    // colum 1 row 3
    temp[7] = element[3] * rhs[4] + element[7] * rhs[5] + element[11] * rhs[6] + element[15] * rhs[7];

    // colum 2 row 0
    temp[8] = element[0] * rhs[8] + element[4] * rhs[9] + element[8] * rhs[10] + element[12] * rhs[11];
    // colum 2 row 1
    temp[9] = element[1] * rhs[8] + element[5] * rhs[9] + element[9] * rhs[10] + element[13] * rhs[11];
    // colum 2 row 2
    temp[10] = element[2] * rhs[8] + element[6] * rhs[9] + element[10] * rhs[10] + element[14] * rhs[11];
    // colum 2 row 3
    temp[11] = element[3] * rhs[8] + element[7] * rhs[9] + element[11] * rhs[10] + element[15] * rhs[11];

    // colum 3 row 0
    temp[12] = element[0] * rhs[12] + element[4] * rhs[13] + element[8] * rhs[14] + element[12] * rhs[15];
    // colum 3 row 1
    temp[13] = element[1] * rhs[12] + element[5] * rhs[13] + element[9] * rhs[14] + element[13] * rhs[15];
    // colum 3 row 2
    temp[14] = element[2] * rhs[12] + element[6] * rhs[13] + element[10] * rhs[14] + element[14] * rhs[15];
    // colum 3 row 3
    temp[15] = element[3] * rhs[12] + element[7] * rhs[13] + element[11] * rhs[14] + element[15] * rhs[15];
    return temp;
}

template <class Item>
Matrix<Item> Matrix<Item>::getRowMajorMatrix()
{
    Matrix<Item> re;
    re[0] = element[0];
    re[1] = element[4];
    re[2] = element[8];
    re[3] = element[12];
    re[4] = element[1];
    re[5] = element[5];
    re[6] = element[9];
    re[7] = element[13];
    re[8] = element[2];
    re[9] = element[6];
    re[10] = element[10];
    re[11] = element[14];
    re[12] = element[3];
    re[13] = element[7];
    re[14] = element[11];
    re[15] = element[15];
    return re;
}
template <class Item>
const Matrix<Item>& Matrix<Item>::loadTranslateMatrix(const Item x,
                                                      const Item y,
                                                      const Item z)
{
    element[0]  = 1;  element[4]  = 0;  element[8]  = 0;  element[12]  = x;
    element[1]  = 0;  element[5]  = 1;  element[9]  = 0;  element[13]  = y;
    element[2]  = 0;  element[6]  = 0;  element[10] = 1;  element[14] = z;
    element[3]  = 0;  element[7]  = 0;  element[11] = 0;  element[15] = 1;
    return *this;
}

template <class Item>
const Matrix<Item>& Matrix<Item>::loadRotateMatrix(const Item angle,
                                                   const Item ax,
                                                   const Item ay,
                                                   const Item az)
{
    
    Item radians, sine, cosine, ab, bc, ca, tx, ty, tz;
    Item axis[3];
    Item mag;
#ifdef USE_FLOAT
	axis[0] = ax;
	axis[1] = ay;
	axis[2] = az;
	mag = sqrt(axis[0]*axis[0] + axis[1]*axis[1] + axis[2]*axis[2]);
	if (mag) {
	  axis[0] /= mag;
	  axis[1] /= mag;
	  axis[2] /= mag;
	}
	radians = angle * DEGREE_TO_RADIAN;
	sine = sin(radians);
	cosine = cos(radians);
	ab = axis[0] * axis[1] * (1 - cosine);
	bc = axis[1] * axis[2] * (1 - cosine);
	ca = axis[2] * axis[0] * (1 - cosine);
	tx = axis[0] * axis[0];
	ty = axis[1] * axis[1];
	tz = axis[2] * axis[2];

	element[0]  = tx + cosine * (1 - tx);
	element[4]  = ab + axis[2] * sine;
	element[8]  = ca - axis[1] * sine;
	element[12]  = 0.0f;
	element[1]  = ab - axis[2] * sine;
	element[5]  = ty + cosine * (1 - ty);
	element[9]  = bc + axis[0] * sine;
	element[13]  = 0.0f;
	element[2]  = ca + axis[1] * sine;
	element[6]  = bc - axis[0] * sine;
	element[10] = tz + cosine * (1 - tz);
	element[14] = 0;
	element[3] = 0;
	element[7] = 0;
	element[11] = 0;
	element[15] = 1;
#endif // USE_FLOAT
        return *this;
}
template <class Item>
const Matrix<Item>& Matrix<Item>::loadScaleMatrix(const Item x,
                                                  const Item y,
                                                  const Item z)
{
    element[0]  = x;  element[4]  = 0;  element[8]  = 0;  element[12]  = 0;
    element[1]  = 0;  element[5]  = y;  element[9]  = 0;  element[13]  = 0;
    element[2]  = 0;  element[6]  = 0;  element[10] = z;  element[14]  = 0;
    element[3]  = 0;  element[7]  = 0;  element[11] = 0;  element[15]  = 1;
    return *this;
}
typedef Matrix<float> gewMat4;
#endif 
